package com.example.demo.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * http or https 请求工具类
 *
 * @author SPL
 * @since 2020-02-04 11:02
 */
@Slf4j
public class HttpUtil {

    /**
     * POST JSON 请求
     *
     * @param url
     * @param jsonData 参数为JSON字符串
     * @return
     */
    public static String doJsonPost(String url, String jsonData, Map<String,String> headers) throws Exception {
        if (StringUtils.isBlank(url)) {
            throw new NullPointerException("doJsonPost request url not allow null or ''! ");
        }
        long beginTime = System.currentTimeMillis();
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        if(headers != null && headers.size() > 0){
            Iterator<Map.Entry<String, String>> iterator = headers.entrySet().iterator();
            while (iterator.hasNext()){
                Map.Entry<String, String> res = iterator.next();
                httpPost.setHeader(res.getKey(),res.getValue());
            }
        }
        httpPost.setEntity(new StringEntity(jsonData, StandardCharsets.UTF_8));
        String response = InnerHttpClient.sendRequest(httpPost);
        long endTime = System.currentTimeMillis();
        log.info("doJsonPost request url:{} successed!\nreqParams is :{},\nresponse content is :{},\ntime-consuming is:{}ms!", url, jsonData, response, endTime - beginTime);
        return response;
    }

    /**
     * POST 请求,可附带参数
     *
     * @param url
     * @param reqParams
     * @return
     */
    public static String doPost(String url, Map<String, String> reqParams) throws Exception {
        return doPost(url, reqParams, null);
    }

    /**
     * POST 请求,可附带参数
     *
     * @param url
     * @param reqParams
     * @return
     */
    public static String doPost(String url, Map<String, String> reqParams, Map<String,String> headers) throws Exception {
        if (StringUtils.isBlank(url)) {
            throw new NullPointerException("doPost request url not allow null or ''! ");
        }
        long beginTime = System.currentTimeMillis();
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        if (reqParams != null && !reqParams.isEmpty()) {
            for (String k : reqParams.keySet()) {
                nvps.add(new BasicNameValuePair(k, reqParams.get(k)));
            }
        }
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8));
        if(headers != null && headers.size() > 0){
            Iterator<Map.Entry<String, String>> iterator = headers.entrySet().iterator();
            while (iterator.hasNext()){
                Map.Entry<String, String> res = iterator.next();
                httpPost.setHeader(res.getKey(),res.getValue());
            }
        }
        String response = InnerHttpClient.sendRequest(httpPost);
        long endTime = System.currentTimeMillis();
        log.info("Post request url:{} successed!\nReqParams:{},\nResponse:{},\ntime-consuming is:{}ms!", url, reqParams, response, endTime - beginTime);
        return response;
    }

    /**
     * GET 请求
     *
     * @param url
     * @return
     */
    public static String doGet(String url) throws Exception {
        return doGet(url, null,null);
    }

    /**
     * GET 请求
     *
     * @param url
     * @return
     */
    public static String doGet(String url, Map<String,String> headers) throws Exception {
        return doGet(url, null,headers);
    }

    /**
     * GET 请求，附带有提供的参数
     *
     * @param url
     * @param reqParams 请求参数
     * @return
     */
    public static String doGet(String url, Map<String, String> reqParams, Map<String, String> headers) throws Exception {
        if (StringUtils.isBlank(url)) {
            throw new NullPointerException("doGet request url not allow null or ''! ");
        }
        long beginTime = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        if (reqParams != null && !reqParams.isEmpty()) {
            for (String k : reqParams.keySet()) {
                if (builder.length() > 0) {
                    builder.append("&");
                }
                builder.append(k).append("=");
                if (StringUtils.isNotBlank(reqParams.get(k))) {
                    builder.append(reqParams.get(k));
                }
            }
        }
        String params = builder.toString();
        if (params.length() > 0) {
            url += url.indexOf("?") != -1 ? "&" + params : "?" + params;
        }
        URL u = new URL(url);
        HttpGet httpGet = new HttpGet(new URI(u.getProtocol(), u.getUserInfo(), u.getHost(), u.getPort(), u.getPath(), u.getQuery(), null));
        if(headers != null && headers.size() > 0){
            Iterator<Map.Entry<String, String>> iterator = headers.entrySet().iterator();
            while (iterator.hasNext()){
                Map.Entry<String, String> res = iterator.next();
                httpGet.setHeader(res.getKey(),res.getValue());
            }
        }
        String response = InnerHttpClient.sendRequest(httpGet);
        long endTime = System.currentTimeMillis();
        log.info("doGet request url:{} successed!\nresponse content is :{},\ntime-consuming is:{}ms!", url, response, endTime - beginTime);
        return response;
    }

    /**
     * 请求客户端实现类
     */
    static class InnerHttpClient {
        //http clilent中从connetcion pool中获得一个connection的超时时间
        final static int REQUEST_TIME_OUT = 5000;
        //链接建立的超时时间
        final static int CONNECT_TIME_OUT = 10000;
        //响应超时时间，超过此时间不再读取响应
        final static int SOCKET_TIME_OUT = 60000;

        /**
         * 请求配置
         *
         * @return
         */
        static RequestConfig config() {
            return RequestConfig.custom()
                    .setConnectionRequestTimeout(0)
                    .setConnectTimeout(0)
                    .setSocketTimeout(0)
                    .build();
        }

        /**
         * 设置请求协议，针对HTTPS协议信任所有证书，同时忽略对HOSTNAME的校验
         *
         * @return
         * @throws KeyManagementException
         * @throws NoSuchAlgorithmException
         * @throws KeyStoreException
         */
        static Registry<ConnectionSocketFactory> registry() throws Exception {
            SSLContext sslcontext = new SSLContextBuilder().loadTrustMaterial(new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            // Create a registry of custom connection socket factories for supported
            // protocol schemes.
            return RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.INSTANCE)
                    .register("https", new SSLConnectionSocketFactory(sslcontext, new HostnameVerifier() {

                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }

                    })).build();
        }

        /**
         * connect链接池管理
         *
         * @return
         * @throws KeyManagementException
         * @throws NoSuchAlgorithmException
         * @throws KeyStoreException
         */
        static PoolingHttpClientConnectionManager connectManager() throws Exception {
            return new PoolingHttpClientConnectionManager(registry());
        }

        /**
         * 创建请求客户端
         *
         * @return
         * @throws Exception
         */
        static HttpClient create() throws Exception {
            return HttpClients.custom()
                    .setDefaultRequestConfig(config())
                    .setConnectionManager(connectManager()).build();
        }

        /**
         * 发送请求模板
         *
         * @param uriRequest
         * @return
         * @throws Exception
         */
        static String sendRequest(HttpUriRequest uriRequest) throws Exception {
            HttpClient httpClient = create();
            try {
                // Create a custom response handler
                ResponseHandler<String> responseHandler = new ResponseHandler<String>() {

                    @Override
                    public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
                        int status = response.getStatusLine().getStatusCode();
                        if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
                            HttpEntity entity = response.getEntity();
                            return entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;
                        } else {
                            throw new ClientProtocolException("Unexpected response status: " + status);
                        }
                    }

                };
                return httpClient.execute(uriRequest, responseHandler);
            } catch (Exception e) {
                throw e;
            } finally {
                HttpClientUtils.closeQuietly(httpClient);
            }
        }
    }
}
